-module(bmula_rfc2234).

-compile([export_all, {parse_transform, optimizer}]).

character(Character) ->
	fun(<<X, Rest/binary>>) when X == Character -> {<<Character>>, Rest};
	   (_) -> false
	end.

range(Start, End) ->
	fun(<<X, Rest/binary>>) when X >= Start; X =< End -> {<<X>>, Rest};
	   (_) -> false
	end.

repetition(Fun) ->
	fun(Bin) ->
		Loop = fun(O, L) ->
			<<Pre:O/binary, Post/binary>> = Bin,
			case Fun(Post) of
				{PreF, _} -> L(O + size(PreF), L);
				false -> {Pre, Post}
			end
		end,
		Loop(0, Loop)
	end.

alternatives(Funs) ->
	fun(Bin) ->
		lists:foldl(fun
			(Fun, false) -> Fun(Bin);
			(_, Result) -> Result
		end, false, Funs)
	end.

optional(Fun1, Fun2) ->
	fun(Bin) -> 
		case Fun1(Bin) of
			false -> Fun2(Bin);
			{Pre1, Post1} ->
				case Fun2(Post1) of
					false -> false;
					{Pre2, Post2} ->
						{<<Pre1/binary, Pre2/binary>>, Post2}
				end
		end
	end.

sequence(Funs) ->
	fun(Bin) ->
		lists:foldl(fun
			(_, false) -> false;
			(Fun, {Pre1, Post1}) ->
				case Fun(Post1) of
					{Pre2, Post2} -> {<<Pre1/binary, Pre2/binary>>, Post2};
					_ -> false
				end
		end, {<<>>, Bin}, Funs)
	end.

'ALPHA'(Bin) -> (alternatives([range($a, $z), range($A, $Z)]))(Bin).

'BIT'(Bin) -> (alternatives([character($0), character($1)]))(Bin).

'CHAR'(Bin) -> (range(16#01, 16#7F))(Bin).

'CR'(Bin) -> (character(16#0D))(Bin).

'CRLF'(Bin) -> (sequence([fun 'CR'/1, fun 'LF'/1]))(Bin).

'CTL'(Bin) -> (alternatives([range(16#00, 16#1F), character(16#7F)]))(Bin).

'DIGIT'(Bin) -> (range(16#30, 16#39))(Bin).

'DQUOTE'(Bin) -> (character(16#22))(Bin).

'HEXDIG'(Bin) -> (alternatives([
	fun 'DIGIT'/1,
	character($A),
	character($B),
	character($C),
	character($D),
	character($E),
	character($F)]))(Bin).

'HTAB'(Bin) -> (character(16#09))(Bin).

'LF'(Bin) -> (character(16#0A))(Bin).

'LWSP'(Bin) ->
	(repetition(alternatives([
		fun 'WSP'/1,
		sequence([fun 'CRLF'/1, fun 'WSP'/1])
	])))(Bin).

'OCTET'(<<X, Rest/binary>>) -> {<<X>>, Rest}.

'SP'(Bin) -> (character(16#20))(Bin).

'VCHAR'(Bin) -> (range(16#21, 16#7E))(Bin).

'WSP'(Bin) -> (alternatives([fun 'SP'/1, fun 'HTAB'/1]))(Bin).

